#include "sdl_renderer.h"

namespace sdl_graphic {

frustum::frustum()
{
}

///uaktualnianie bryły widoku

void frustum::update(const matrix4x4f &p, const matrix4x4f &mv)
{
    _msg("frustum", 4, "uaktualnianie bryły widoku");
    modelview = mv;
    projection = p;
    combined = modelview*projection;

    mvector4f r0 = combined.row(0);
    mvector4f r1 = combined.row(1);
    mvector4f r2 = combined.row(2);
    mvector4f r3 = combined.row(3);

    v_right = r3 - r0;
    v_left = r3 + r0;
    v_bottom = r3 + r1;
    v_top = r3 - r1;
    v_far = r3 - r2;
    v_near = r3 + r2;

    v_right.normalize();
    v_left.normalize();
    v_bottom.normalize();
    v_top.normalize();
    v_far.normalize();
    v_near.normalize();
}

renderer::renderer(const std::string & n) :
sdl_layer(300, n)
{
    _msg("renderer", 20, "tworzenie renderera: " << name());
}

renderer::~renderer()
{
    _msg("renderer", 10, "kasowanie renderera: " << name());
}

void renderer::set_world(const sdl_ptr<world> & w)
{
    if (!w)
        _msg("renderer", 40, "ostrzeżenie: podany wskaźnik na nic nie pokazuje");

    _msg("renderer", 20, "ustawiam świat: " << w->name());

    current_world = w;
}

void renderer::update()
{
    if (!current_world) return;
    //TODO: BSP albo frustum cull
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnableClientState(GL_INDEX_ARRAY);

    // frustum update
    /*
    matrix4x4f p;
    glGetFloatv(GL_PROJECTION_MATRIX,(float*)(p));
    view.update(p,current_world->observer->get_transform());
     */
    // ile wycięto
    int culled = 0;
    for (std::list<sdl_ptr<entity> >::iterator i = current_world->population.begin(); i != current_world->population.end(); i++) {
        const sdl_ptr<entity> & pert = *i;
        // przykład wiadomości, która psuje bardzo wydajność nawet przy wysokim progu filtrowania
        //_msg("renderer",2,"biorę się za byt "<<pert->name());

        if (!pert) _msg("renderer", 40, "ostrzeżenie: wskaźnik do bytu jest pusty!");

        // frustum cull
        /*
        if(!view.contained(pert)) {
                culled++;
                continue;
        }
         */

        // ustawiamy bufory
        setup_buffers(pert->body);
        glMatrixMode(GL_MODELVIEW);
        glLoadMatrixf((float*) (current_world->observer->get_transform()));
        glMultMatrixf((float*) (pert->transform));

        // skanujemy mapy materiałowe w ciele bytu
        for (std::vector<mesh::matmap>::iterator mm = pert->body->matmaps.begin(); mm != pert->body->matmaps.end(); mm++) {
            setup_material(mm->mat, pert->body->texcoords);
            glDrawRangeElements(GL_TRIANGLES, mm->start, mm->end, mm->count, GL_UNSIGNED_INT, NULL);
        }
    }

    _msg("frustum", 6, "wycięto " << culled << " bytów");
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_INDEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
}

void renderer::setup_buffers(const sdl_ptr<mesh> & e)
{
    if (!e)
        _msg("renderer", 40, "pusty wskaźnik bytu");

    e->vertices->create_vbo();
    e->normals->create_vbo();
    e->colors->create_vbo();
    e->indices->create_vbo();
    for (int i = 0; i < e->texcoords.size(); i++)
        e->texcoords[i]->create_vbo();

    e->vertices->init_update();
    e->normals->init_update();
    e->colors->init_update();
    for (int i = 0; i < e->texcoords.size(); i++)
        e->texcoords[i]->init_update();

    e->vertices->do_update();
    e->normals->do_update();
    e->colors->do_update();
    for (int i = 0; i < e->texcoords.size(); i++)
        e->texcoords[i]->do_update();

    e->vertices->done_update();
    e->normals->done_update();
    e->colors->done_update();
    for (int i = 0; i < e->texcoords.size(); i++)
        e->texcoords[i]->done_update();

    e->indices->update_vbo();

    // bindujemy dane
    glBindBuffer(GL_ARRAY_BUFFER, e->vertices->vbo);
    glVertexPointer(3, GL_FLOAT, 0, 0);
    glBindBuffer(GL_ARRAY_BUFFER, e->normals->vbo);
    glNormalPointer(GL_FLOAT, 0, 0);
    glBindBuffer(GL_ARRAY_BUFFER, e->colors->vbo);
    glColorPointer(4, GL_FLOAT, 0, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, e->indices->vbo);
}

void renderer::setup_material(const sdl_ptr<material> & mat, const std::vector<sdl_ptr<tex_vmap> > & t)
{
    if (!mat)
        _msg("renderer", 40, "ostrzeżenie: wskaźnik do materiału jest pusty!");

    static int maxtex;
    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &maxtex);
    static const int texlevel = GL_TEXTURE0;
    if (t.size() == 0)
        _msg("renderer", 4, "ostrzeżenie: nie ma żadnych map współrzędnych tekstur! Czy mapy współrzędnych zostały utworzone?");

    if (mat->textures.size() == 0) {
        _msg("renderer", 4, "ostrzeżenie: nie ma żadnych tekstur! Czy tekstury zostały utworzone?");
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glDisable(GL_TEXTURE_2D);
        return;
    } else
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    if (mat->textures.size() > t.size())
        _msg("renderer", 4, "ostrzeżenie: inna liczba tekstur i map współrzędnych! Czy tekstury i mapy były dobrze zainicjalizowane?");
    if (mat) {
        if (mat->textures.size() <= t.size())
            for (int i = 0; i < mat->textures.size() && i < maxtex; i++) {
                if (!(mat->textures[i])) continue;
                if (!mat->textures[i]->invideo) {
                    _msg("renderer", 4, "tekstura niezaładowana! " << mat->textures[i]->name());
                    mat->textures[i]->load();
                }
                if (!mat->textures[i]->valid()) continue; //fatalny błąd

                glActiveTexture(texlevel + i);

                glEnable(GL_TEXTURE_2D);
                glBindBuffer(GL_ARRAY_BUFFER, t[i]->vbo);
                glBindTexture(GL_TEXTURE_2D, mat->textures[i]->texture);
                glClientActiveTexture(texlevel + i);
                glTexCoordPointer(2, GL_FLOAT, 0, 0);
            }
        if (mat->double_sided) {
            glDisable(GL_CULL_FACE);
        } else {
            glEnable(GL_CULL_FACE);
        }
        glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*) mat->ambient);
        glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*) mat->diffuse);
        glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*) mat->specular);
        glMaterialfv(GL_FRONT, GL_EMISSION, (const float*) mat->emission);
        glMaterialf(GL_FRONT, GL_SHININESS, mat->shininess);
    }

}

/*
void renderer::setup_buffers(int & offset, sdl_ptr<vertex_vmap> & v, sdl_ptr<vertex_vmap> & n, sdl_ptr<color_vmap> & c, std::vector<sdl_ptr<tex_vmap> > & t, sdl_ptr<material> & mat, matrix4x4f & transform) {

        glBindBuffer(GL_ARRAY_BUFFER,v->vbo);
        glVertexPointer(3,GL_FLOAT,0,0);
        glBindBuffer(GL_ARRAY_BUFFER,n->vbo);
        glNormalPointer(GL_FLOAT,0,0);
        glBindBuffer(GL_ARRAY_BUFFER,c->vbo);
        glColorPointer(4,GL_FLOAT,0,0);

        //TODO: oświetlenie

        if (t.size()==0)
                _msg("renderer",10,"ostrzeżenie: nie ma żadnych map współrzędnych tekstur! Czy mapy współrzędnych zostały utworzone?");

        if (mat->textures.size()==0)
                _msg("renderer",10,"ostrzeżenie: nie ma żadnych tekstur! Czy tekstury zostały utworzone?");

        if (mat->textures.size()>t.size())
                _msg("renderer",4,"ostrzeżenie: inna liczba tekstur i map współrzędnych! Czy tekstury i mapy były dobrze zainicjalizowane?");


        if (mat)
                if (mat->textures.size()<=t.size())
                        for (int i=0;i<mat->textures.size() && i<maxtex;i++) {
                                if (!mat->textures[i]->invideo) {
                                        _msg("renderer",4,"tekstura niezaładowana! "<<mat->textures[i]->name());
                                        mat->textures[i]->load();
                                }

                                glActiveTexture(texlevel+i);

                                glEnable(GL_TEXTURE_2D);
                                glBindBuffer(GL_ARRAY_BUFFER,t[i]->vbo);
                                glBindTexture(GL_TEXTURE_2D,mat->textures[i]->texture);
                                glClientActiveTexture(texlevel+i);
                                glTexCoordPointer(2,GL_FLOAT,0,0);
                        }


        //TODO: zmiana w mesh::matmap żeby użyć glDrawRangeElements

}
 */
}; //namespace

template<> sdl_ptr<sdl_graphic::renderer> sdl_singleton<sdl_graphic::renderer>::__field = NULL;
